#include "image.h"

//------------------------------------------------------------------------------
Image::Image (const QImage &other, const Session &s) : QImage (other), m_session (s), m_blackCount (-1)

{
    if (format () != Format_ARGB32)
        convertToFormat (Format_ARGB32);
}
Image::Image (Image const &other) : QImage (other), m_session (other.session ()), m_blackCount (-1)
{
}
//------------------------------------------------------------------------------
Image::Image(const QSize &size, const Session &s) : QImage (size, Format_ARGB32), m_session (s), m_blackCount (-1)
{
}
//------------------------------------------------------------------------------
Image::Image(const QString &path) : QImage (path), m_blackCount (-1)
{
    if (format () != Format_ARGB32)
        convertToFormat (Format_ARGB32);

    m_session.setFileName (path);
}
//------------------------------------------------------------------------------
void Image::clear()
{
    m_blackCount = -1;
    m_session.clear ();
    *this = Image (QSize (0,0));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
QString Image::properties () const
{
    QStringList propertyList;

    propertyList << QObject::tr ("Largeur : %1 pixels (%2 mm)")
                    .arg (width ())
                    .arg (widthMM ());

    propertyList << QObject::tr ("Hauteur : %1 pixels (%2 mm)")
                    .arg (height ())
                    .arg (heightMM ());

    propertyList << QObject::tr ("Surface : %1 pixels (%2 mm²)")
                    .arg (width () * height ())
                    .arg (widthMM () * heightMM ());

    propertyList << QObject::tr ("Résolution : %1 x %2 points par pouce")
                    .arg (dpmToDpi (dotsPerMeterX ()))
                    .arg (dpmToDpi (dotsPerMeterY ()));

    propertyList << QObject::tr ("Format : %1 bit(s) par pixel").arg (bitPlaneCount ());

    const int cc = colorCount ();

    if (cc) {
        propertyList << QObject::tr ("%1 couleur(s)",0, cc);

        if (isGrayscale ()) {
            propertyList << QObject::tr ("(image en niveaux de gris)");
        }
    }

    const int mem = byteCount ();
    QString message (QObject::tr ("Mémoire occupée : %1 %2"));

    if (mem > (1024*1024)) {
        propertyList << message.arg (mem / (1024*1024)).arg (QObject::tr ("Mio"));
    }
    else {
        if (mem > (1024)) {
            propertyList << message.arg (mem / 1024).arg (QObject::tr ("kio"));
        }
        else {
            propertyList << message.arg (mem).arg (QObject::tr ("octets"));
        }
    }

    foreach (const QString &prop, textKeys ()) {
        propertyList << QString ("%1 : %2")
                        .arg (prop.isEmpty () ? "?" : prop)
                        .arg (text (prop));
    }

    return propertyList.join ("\n");

}
//------------------------------------------------------------------------------
void Image::toMono (quint8 const contrast)
{
    if (size ().isEmpty ()) return;

    CHRONO_START;
    Pixel32 *scanSource = bits ();
    register int index = width () * height ();

    if (index % 2) {
        while (index--) {
            (scanSource++)->convertToMono (contrast);
        }
    }
    else {
        index /= 2;
        if (index % 2) {
            while (index--) {
                (scanSource++)->convertToMono (contrast);
                (scanSource++)->convertToMono (contrast);
            }
        }
        else {
            index /= 2;
            while (index--) {
                (scanSource++)->convertToMono (contrast);
                (scanSource++)->convertToMono (contrast);
                (scanSource++)->convertToMono (contrast);
                (scanSource++)->convertToMono (contrast);
            }
        }
    }
    CHRONO_END;
}
//------------------------------------------------------------------------------
int Image::blackCount ()
{
    CHRONO_START;
    if (m_blackCount == -1) {
        int count = 0;
        Pixel32 const *scanSource = constBits ();
        register int index = width () * height ();
        while (index--) {
            if ((*scanSource++) == Pixel32::BlackColor) ++count;
        }

        m_blackCount = count;
    }
    CHRONO_END;
    return m_blackCount;
}
//------------------------------------------------------------------------------
int Image::whiteCount ()
{
    return ((width () * height ()) - blackCount ());
}
//------------------------------------------------------------------------------
QRect Image::rectOfInterest (const Pixel32 color,
                             const QRect& fromRect) const
{
    if (size ().isEmpty ()) return QRect (0,0,0,0);
    if (not fromRect.isValid () or fromRect.isNull () or fromRect.isEmpty ()) return QRect (0,0,0,0);

    const int top = fromRect.top ();
    const int bottom = fromRect.bottom ();
    const int left = fromRect.left ();
    const int right = fromRect.right ();
    int tlx = right;
    int tly = bottom;
    int brx = left;
    int bry = top;
    const int w = width ();

    const Pixel32 *scan = constScanLine (top);
    for (int y = top ; y <= bottom ; ++y, scan += w) {
        bool foundInLine = false;
        for (int x = left ; x <= right ; ++x) {
            if (scan[x] == color) {
                foundInLine = true;
                if (x > brx) brx = x;
                if (x < tlx) tlx = x;
            }
        }
        if (foundInLine) {
            if (y > bry) bry = y;
            if (y < tly) tly = y;
        }
    }

    QRect result (QPoint (tlx, tly), QPoint (brx, bry));
    if (!result.isValid ())
        result.setRect (0,0,0,0);

    return result;
}
//-----------------------------------------------------
QRect Image::minimumRectOfInterest () const
{
    QRect currentRect (rect ());
    Pixel32 currentColor = *constBits ();
    currentColor.invertColor ();
    QRect resultRect;

    resultRect = rectOfInterest (currentColor, currentRect);
    if (resultRect.isNull ()) return currentRect;

    while (resultRect != currentRect) {
        currentRect = resultRect;
        currentColor.invertColor ();
        QRect tempRect (rectOfInterest (currentColor, currentRect));
        if (!tempRect.isNull ())
            resultRect = tempRect;
    }

    return resultRect;
}
//-----------------------------------------------------------------
/**
 * @brief Image::copyInto
 * copie la source dans une image dest au point (x, y)
 * @param dest
 * @param x
 * @param y
 */
void Image::copyInto (const QImage &src, QImage *dest, int x, int y)
{
    CHRONO_START;
    const int * source = (const int *) src.constBits ();
    const int wordsToCopy = src.width ();

    int *scanDest = (int *) dest->scanLine (y);
    scanDest += x;
    const int toWidth = dest->width ();

    int h = src.height ();

    while (h --) {
        Tools::memCpy32 (scanDest, source, wordsToCopy);
        source += wordsToCopy;
        scanDest += toWidth;
    }
    CHRONO_END;

}
//-------------------------------------------------------------
/**
 * @brief ImgMgr::copy32
 * retourne la copie du rectangle rect de source
 * @param source
 * @param rect
 * @return
 */
Image Image::copyRect(QRect const &rectangle) const
{
    if (rectangle == rect ()) {
        return *this;
    }

    //    return copy (rectangle);

    CHRONO_START;
    QRect destRect = rectangle & (rect ());

    Image dest (destRect.size ());

    qint32 *scanSource = (qint32 *) scanLine (destRect.y ());
    scanSource += destRect.x();
    const int sourceIncr = width ();

    qint32 *scanDest = (qint32 *) dest.bits ();
    qint32 const wordsToCopy = dest.width ();

    qint32 destH = destRect.height ();
    while (destH--) {
        Tools::memCpy32 (scanDest, scanSource, wordsToCopy);
        scanDest += wordsToCopy;
        scanSource += sourceIncr;
    }
    CHRONO_END;

    return dest;
}

//----------------------------------------------------------------------------------------------------
Image Image::fusionV (const QList<QImage> &list)
{
    if (list.isEmpty ()) return QImage (QSize (0,0), QImage::Format_ARGB32);

    if (list.count () == 1) {
        return list.at (0);
    }

    CHRONO_START;

    int wresult = 0;
    int hresult = 0;

    foreach (const QImage &image, list) {
        wresult = Tools::max (wresult, image.width ());
        hresult += image.height ();
    }

    QImage result (QSize (wresult, hresult), Format_ARGB32);

    quint32 * scanDest = (quint32 *) result.bits ();

    Tools::memSet32 (scanDest, Pixel32::WhiteColor, wresult * hresult);	// remplit dest de blanc

    int y = 0;
    foreach (const QImage &image, list) {
        copyInto (image, &result, 0, y);
        y += image.height ();
    }


    CHRONO_END;

    return result;
}
//--------------------------------------------------------------------------------------------------
void Image::invertOrphanPixels(const int minNeighbourCount)
// maxNeighbours = nombre de pixels voisins de même couleur maxi
// en-dessous duquel le pixel change de couleur
{
    const int w = width ();

    if (w < 3 or height () < 3) return;

    CHRONO_START;
    m_minNeighbourCount = minNeighbourCount;

    uint *scanLine = ((uint *) bits ()) + w;
    const int limitX = w-1;

    int y = height () - 3;
    while (y--) {
        for (int x = 1 ; x < limitX ; ++x) {
            invertIfOrphan (scanLine + x);
        }
        scanLine += w;
    }

    CHRONO_END;
}

// (data[x >> 3] >> (7 - (x % 8))) & 1


//------------------------------------------------------------------------------
QImage Image::toPreview () const
{
    if (size ().isEmpty ()) return QImage ();

    return fusionV (split ());

    //    return (workImage);
}
//--------------------------------------------------------------
QList<QImage> Image::split () const
{
    if (size ().isEmpty ()) return QList<QImage> ();

    Image workImage (*this);

    QList<QImage> result;

    if (not session ().redRects ().isEmpty ()) {
        QPainter painter (&workImage);
        painter.setBrush (Qt::white);
        painter.setPen (Qt::NoPen);

        painter.drawRects (redRects ().toVector ());
    }

    if (greenRects ().isEmpty ()) {
        result <<  workImage.extract (workImage.rect (), scale (), contrast ());
    }
    else {
        foreach (const QRect &greenRect, (greenRects ())) {
            result << workImage.extract (greenRect, scale (), contrast ());
        }
    }



    return result;
}
//------------------------------------------------------------------------------
QImage Image::extract (QRect const &rect, double const scale, quint8 const contrast) const
{

    // 1/ extraire le rect

    Image workImage = copyRect (rect);

    // 2/ le mettre à l'échelle

    if (scale != 1.) {
        const QTransform matrix = QTransform::fromScale (scale, scale);
        workImage = workImage.transformed (matrix, Qt::SmoothTransformation);
    }

    // 3/ le passer en N&B

    workImage.toMono (contrast);

    // 4/ l'optimiser

    if (session ().mustCleanImage ()) {
        if (mustInvertOrphans ()) {
            workImage.invertOrphanPixels (minPixNeighbours ());
        }
        workImage = workImage.extractROI (); // Region Of Interest
        if (mustInvertColors ()) {
            if (workImage.blackCount () > workImage.whiteCount ()) {
                workImage.invertPixels ();
            }
        }
    }

    return workImage;
}
